package cn.codergege.gradle.plugin.kotlin

import cn.codergege.gradle.plugin.generateSourceCode
import org.gradle.api.tasks.TaskAction

open class KtBootBasicStage: KtJavaBasicStage() {

    @TaskAction
    override fun run() {
        super.run()
        // tag::main/kotlin[]
        // 生成 spring boot main
        generateSourceCode(mainKtDir, packageDir, mainFileName, codeAppMain)
        // cn.codergege.demo.config.DruidDataSourceConfig
        generateSourceCode(mainKtDir, configPackageDir, druidDataSourceConfigFileName, codeDruidDataSourceConfig)
        // cn.codergege.demo.dao.PersonRepository
        generateSourceCode(mainKtDir, daoPackageDir, personRepoFileName, codePersonRepo)
        // cn.codergege.demo.dev.DbInit
        generateSourceCode(mainKtDir, devPackageDir, dbInitFileName, codeDbInit)
        // cn.codergege.demo.entity.Person
        generateSourceCode(mainKtDir, entityPackageDir, personFileName, codePerson)
        // cn.codergege.demo.service.PersonService
        generateSourceCode(mainKtDir, servicePackageDir, personServiceFileName, codePersonService)
        // cn/codergege/demo/utils/utils.kt
        generateSourceCode(mainKtDir, utilsPackageDir, utilsFileName, codeUtils)
        // end::main/kotlin[]

        // tag::main/resources[]
        // application.yml
        generateSourceCode(mainResourceDir, "", appYmlFileName, codeAppYml)
        // end::main/resources[]

        // tag::test/kotlin[]
        // cn.codergege.demo.${convertedProjectName}ApplicationTest
        generateSourceCode(testKtDir, packageDir, appTestFileName, codeAppTest)
        // cn.codergege.demo.bean.Greeter
        generateSourceCode(testKtDir, beanPackageDir, greeterFileName, codeGreeter)
        // cn.codergege.demo.service.PersonServiceTest
        generateSourceCode(testKtDir, servicePackageDir
            , personServiceTestFileName, codePersonServiceTest)
        // end::test/kotlin[]
    }

    override var readmeMd = """# ${project.name} #

## Quik Start ##

```bash
gradle test
gradle
```

## 使用的技术 ##

* Kotlin
* Spring Boot
    + data-jpa
* 数据库(单数据源)
    + jpa
    + mysql, H2
    + druid
    + querydsl
* Test
    + spring boot test
    + Junit5

## Stage 插件 ##

使用 ktBootBasic 生成项目
`gradle ktBootBasic`

## 修改为 Oracle 数据库 ##

* 新建 lib 目录, 复制 ojdbc6.jar 到 lib, 刷新 gradle
* 修改 yml 配置文件, 主要需要修改以下内容
```yaml
url: jdbc:oracle:thin:@10.22.10.120:1521:orcl
username: base
password: base
driver-class-name: oracle.jdbc.OracleDriver
...
jpa:
    database: ORACLE
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.Oracle9iDialect
    hibernate:
      ddl-auto: update
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
```

## Todo ##

"""

    // tag::main/kotlin[]
    override var codeAppMain = """package $packageName
import $packageName.dev.DbInit
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Profile
import org.springframework.core.env.Environment
import org.springframework.core.env.StandardEnvironment

@SpringBootApplication
class ${convertedProjectName}Application{
    @Autowired
    lateinit var env: Environment
    // for init dev data
    // 最好是在 CommandLineRunner 中直接通过 applicationContext 获取
    @Autowired
    var dbInit: DbInit? = null

    @Bean
    @Profile("dev")
    fun init() = CommandLineRunner {
        println("Active profiles from env: ${'$'}{env.activeProfiles[0]}")
        // Do it only the first time!
        // 如果是 dev 模式, 对数据库进行初始化
        // if(env.activeProfiles[0] == "dev") dbInit?.initData()

        // Todo
    }
}

fun main(args: Array<String>) {
    println("Demo 开始运行...")
    runApplication<${convertedProjectName}Application>(*args) {
        val env = StandardEnvironment()
//        env.setActiveProfiles("dev")
        setEnvironment(env)
    }
}
"""
    // cn.codergege.demo.config.DruidDataSourceConfig
    open var configPackageDir = "$packageDir/config"
    open var druidDataSourceConfigFileName = "DruidDataSourceConfig.kt"
    open var codeDruidDataSourceConfig = """package $packageName.config

import com.alibaba.druid.pool.DruidDataSource
import com.alibaba.druid.support.http.StatViewServlet
import com.alibaba.druid.support.http.WebStatFilter
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.boot.web.servlet.ServletRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import java.util.*
import javax.sql.DataSource

@Configuration
@Profile("dev", "prod")
class DruidDataSourceConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    fun druidDataSource(): DataSource {
        return DruidDataSource()
    }
}
"""
    // cn.codergege.demo.dao.PersonRepository
    var daoPackageDir = "$packageDir/dao"
    var personRepoFileName = "PersonRepository.kt"
    var codePersonRepo = """package $packageName.dao

import $packageName.entity.Person
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.querydsl.QuerydslPredicateExecutor

interface PersonRepository: JpaRepository<Person, Int>
    , QuerydslPredicateExecutor<Person> {
    fun findByName(name: String): List<Person>?
}
"""
    // cn.codergege.demo.dev.DbInit
    var devPackageDir = "$packageDir/dev"
    var dbInitFileName = "DbInit.kt"
    var codeDbInit = """package $packageName.dev

import $packageName.dao.PersonRepository
import $packageName.entity.Person
import $packageName.utils.toDate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component

@Component
@Profile("dev")
class DbInit {
    @Autowired
    lateinit var personRepository: PersonRepository
//    @Value("\${'$'}{spring.profiles.active}")
//    lateinit var activeProfiles: String
//    @Value("\${'$'}{spring.jpa.hibernate.ddl-auto}")
//    lateinit var ddlAuto: String

    fun initData() {
        println("DEV mode: Initialize database with test data...")
        // 给 Person 表添加数据
        val persons = listOf<Person>(
            Person(null, "AA", 1, "1986-05-29".toDate(), "AA@163.com"),
            Person(null, "BB", 0, null, "AA@163.com")
        )
        personRepository.saveAll(persons)
    }
}
"""
    // cn.codergege.demo.entity.Person
    var entityPackageDir = "$packageDir/entity"
    var personFileName = "Person.kt"
    var codePerson = """package $packageName.entity

import java.io.Serializable
import java.util.*
import javax.persistence.*

@Entity
class Person(
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    var id: Int?,
    var name: String,
    var gender: Int?,
    @Temporal(TemporalType.DATE)
    var birthday: Date?,
    var email: String?,
    @Column(length = 3500)
    var description: String = "",
    var createTime: Date = Date()
): Serializable {
    override fun toString(): String {
        return "Person(id=${'$'}id, name='${'$'}name', gender=${'$'}gender, birthday=${'$'}birthday, email=${'$'}email, description='${'$'}description', createTime=${'$'}createTime)"
    }
}
"""
    // cn.codergege.demo.service.PersonService
    var servicePackageDir = "$packageDir/service"
    var personServiceFileName = "PersonService.kt"
    var codePersonService = """package $packageName.service

import $packageName.dao.PersonRepository
import $packageName.entity.Person
import $packageName.entity.QPerson.person
import com.querydsl.core.types.Predicate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cache.annotation.CacheConfig
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.CachePut
import org.springframework.cache.annotation.Cacheable
import org.springframework.stereotype.Service

@CacheConfig(cacheNames = arrayOf("person"))
@Service
class PersonService {
   @Autowired
   lateinit var personRepository: PersonRepository

   /**
    * 缓存:
    *    1. @Cacheable 常用于查询
    *       默认 key :
    *          1) 没有参数, key = new SimpleKey();
    *          2) 1 个参数, key 等于这个参数
    *          3) 多个参数, key = new SimpleKey(多个参数)
    *    2. @CachePut 常用于更新, 将更新后的返回值缓存起来(同步更新缓存)
    *       使用 @CachePut(value = "person", key = "#result.id")
    *    3. @CacheEvict 常用于删除, 将删除的结果从缓存中删除
    */

   //   @Cacheable("person")
   @Cacheable
   fun findPersonById(id: Int): Person {
      println("查询 id 是 ${'$'}id 的 person.")
      return personRepository.findById(id).get()
   }
   @CachePut(value = "person", key = "#result.id")
   fun updatePerson(person: Person): Person {
      return personRepository.saveAndFlush(person)
   }
   @CacheEvict(value = "person", key = "#id")
   fun deletePerson(id: Int) {
      personRepository.deleteById(id)
   }

   @Cacheable(value = "person")
   fun findPersons(name: String): List<Person>? {
      return personRepository.findByName(name)
   }

   fun findPersonsByGender(gender: Int): Iterable<Person>? {
      val predicate: Predicate = person.gender.eq(gender)
      return personRepository.findAll(predicate)
   }
}
"""
    // cn/codergege/demo/utils/utils.kt
    var utilsPackageDir = "$packageDir/utils"
    var utilsFileName = "utils.kt"
    var codeUtils = """package $packageName.utils

import java.text.SimpleDateFormat
import java.util.*

fun String.toDate(): Date {
    return SimpleDateFormat("yyyy-MM-dd").parse(this)
}
"""
    // end::main/kotlin[]

    // tag::main/resources[]
    // application.yml
    open var appYmlFileName = "application.yml"
    open var codeAppYml = """# 所有 profiles 中都生效
author: codergege
server:
  port: 8888
spring:
  profiles:
    #设置激活的 profile
    active: dev
---
# 默认 profiles
spring:
  profiles: default
greeting: This is default environment
---
# dev profiles
spring:
  profiles: dev
  # datasource
  datasource:
    # 数据源基本配置
    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    # druid 数据源配置
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    # 配置监控统计拦截的 filters, 去掉后监控界面 sql 无法统计, wall 用于防火墙
    # filters: stat,wall,log4j
    filters: stat,wall
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true; druid.stat.slowSqlMillis=500
  # jpa
  jpa:
    database: MYSQL
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
    hibernate:
      ddl-auto: update
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
greeting: This is development environment
---
# test profiles
spring:
  profiles: test
  # datasource: H2
  jpa:
    database: H2
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create-drop
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
greeting: This is test environment
---
# prod profiles
spring:
  profiles: prod
  # datasource
  datasource:
    # 数据源基本配置
    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    # druid 数据源配置
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    # 配置监控统计拦截的 filters, 去掉后监控界面 sql 无法统计, wall 用于防火墙
    # filters: stat,wall,log4j
    filters: stat,wall
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true; druid.stat.slowSqlMillis=500
  # jpa
  jpa:
    database: MYSQL
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
    hibernate:
      ddl-auto: none
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
greeting: This is production environment
"""
    // end::main/resources[]
    // tag::test/kotlin[]
    // cn.codergege.demo.${convertedProjectName}ApplicationTest
    open var appTestFileName = "${convertedProjectName}ApplicationTest.kt"
    override var codeAppTest = """package $packageName

import $packageName.bean.Greeter
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.core.env.Environment
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
import javax.sql.DataSource

@ExtendWith(SpringExtension::class)
@SpringBootTest
@ActiveProfiles("test")
class ${convertedProjectName}ApplicationTest {

    @Autowired
    lateinit var greeter: Greeter
    @Autowired
    lateinit var dateSource: DataSource
    @Autowired
    lateinit var env: Environment

    @Test
    fun testApp() {
        println("测试...")
        assertTrue(true)
        println(dateSource::class.java)
        println(env.activeProfiles)
    }

    @Test
    fun testGreeter() {
        assertEquals("This is test environment", greeter.greetingMsg)
    }
}
"""
    // cn.codergege.demo.bean.Greeter
    open var beanPackageDir = "$packageDir/bean"
    open var greeterFileName = "Greeter.kt"
    open var codeGreeter = """package $packageName.bean

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class Greeter {
    // ${'$'} 需要转义, 因为在 kotlin 中 ${'$'} 是字符串占位符
    @Value("\${'$'}{greeting}")
    lateinit var greetingMsg: String
}
"""
    // cn.codergege.demo.service.PersonServiceTest
    open var personServiceTestFileName = "PersonServiceTest.kt"
    open var codePersonServiceTest = """package $packageName.service

import $packageName.dao.PersonRepository
import $packageName.entity.Person
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.junit.jupiter.SpringExtension
import java.util.*

@ExtendWith(SpringExtension::class)
@SpringBootTest
@ActiveProfiles("test")
class PersonServiceTest {
    private val log = LoggerFactory.getLogger(this::class.java)
    @Autowired
    lateinit var personService: PersonService
    @Autowired
    lateinit var personRepository: PersonRepository

    @BeforeEach
    fun initH2() {
        val persons = listOf<Person>(
            Person(null,"codergege", 1, Date(), "codergege@163.com"),
            Person(null, "djh", 0, null, null)
        )
        personRepository.saveAll(persons)
    }

    @Test fun testFindPersons() {
        val persons = personService.findPersons("codergege")
        assertEquals(persons?.size, 1)
        persons?.forEach(::println)
    }
}
"""
    // end::test/kotlin[]
}